﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RecommenderSystem.misc;

namespace RecommenderSystem.algorithms.memoryBased
{
    class MyBitArray
    {

        private bool[,] _data;
        //private List<string> _attrs;
        Dictionary<string, int> _attridxs;
        Dictionary<string, int> _useridx;

        public MyBitArray(List<string> attrs, Dictionary<string, User> users)
        {
            //this._attrs = attrs;
            this._data = new bool[users.Count,attrs.Count];
            this._attridxs = new Dictionary<string,int>();
            for (int i=0;i<attrs.Count;i++)
            {
                this._attridxs[attrs[i]] = i;
            }
            this._useridx = new Dictionary<string,int>();
            int c = 0;
            foreach (User u in users.Values)
            {
                int uidx = c++;
                this._useridx[u.getUserId()] = uidx;
                foreach (string s in attrs)
                {
                    int iidx = this._attridxs[s];
                    this._data[uidx, iidx] = true;
                }
            }
        }

        public Boolean get(string uid, string iid)
        {
            return this._data[this._useridx[uid], this._attridxs[iid]];
        }
    }
    class Node
    {
        public string attr;
        public bool isLeaf;
        public bool ans;
        public int wp;
        public int wn;
        public int nwp;
        public int nwn;
        public ICollection<User> users;
        public Dictionary<bool, Node> vals;
        public double rate;

        public Node() {
            this.vals = new Dictionary<bool, Node>();
        }
        public Node(string attr, bool ans, bool isleaf, ICollection<User> users, string target)
        {
            this.attr = attr;
            this.users = users;
            this.ans = ans;
            this.isLeaf = isleaf;
            if (!ans)
                this.rate = 1;
            else
            {
                this.rate = 0;
                int count = 0;
                foreach (User u in users)
                {
                    double r = u.getItemRate(target);
                    if (r > 0)
                    {
                        this.rate += r;
                        count++;
                    }
                }
                this.rate /= count;
            }

            this.vals = new Dictionary<bool, Node>();
        }
        public void addChild(bool val, Node child)
        {
            this.vals[val] = child;
        }
    }

    class Tree
    {
        private Node _root;


        public Tree(ICollection<User> users, ICollection<string> attrs, string target)
        {
            int spos = users.Where(u => u.getItemRate(target) > 0).Count();
            int sneg = users.Count - spos;
            this._root = buildTree(users,attrs, target,spos,sneg);
        }
        public Node getRoot()
        {
            return this._root;
        }

        public int getDepth()
        {
            return this.getDepth(this._root);
        }

        private int getDepth(Node root)
        {
            if (root.isLeaf)
                return 1;
            List<int> res = new List<int>();
            foreach (Node n in root.vals.Values)
                res.Add(this.getDepth(n));
            return res.Max() + 1;
        }

        public int getSize()
        {
            return this.getSize(this._root);
        }

        private int getSize(Node root)
        {
            if (root.isLeaf)
                return 1;
            int res = 1;
            foreach (Node n in root.vals.Values)
                res += this.getSize(n);
            return res;
        }

        public void setRoot(Node root)
        {
            this._root = root;
        }

        private Node getAnswerHelper(User u, Node n)
        {
            if (n.isLeaf)
                return n;
            if (u.getItemRate(n.attr) > 0)
                return getAnswerHelper(u, n.vals[true]);
            else
                return getAnswerHelper(u, n.vals[false]);
        }

        public Node getAnswerNode(User u)
        {
            return getAnswerHelper(u,this._root);
        }

        public Node buildTree(ICollection<User> users, ICollection<string> attrs, string target, int spos, int sneg)
        {
            int rateth = 2;
            Node newnode = null;
            double ratio = (double)spos / (double)sneg;
            double ratioth = 0.1;
            if (ratio < ratioth || ratio > 1/ratioth)
            {
                newnode = new Node("", spos > sneg, true, users, target);
                return newnode;
            }
            newnode = new Node();
            Dictionary<string, int> watchedtgt = new Dictionary<string, int>();

            double maxGain = 0;
            HashSet<User> watched = new HashSet<User>();
            HashSet<User> notwatched = new HashSet<User>();
            foreach (string itemid in attrs)
            {
                int wp = 0;
                int wn = 0;
                int nwp = 0;
                int nwn = 0;
                foreach (User u in users)
                {
                    int flag = 0;
                    if (u.getItemRate(itemid) > rateth)
                    {
                        flag += 1;
                    }
                    if (u.getItemRate(target) > rateth)
                    {
                        flag += 2;
                    }
                    switch (flag) {
                        case 0:
                            nwn++; notwatched.Add(u); break;
                        case 1:
                            wn++; watched.Add(u); break;
                        case 2:
                            nwp++; notwatched.Add(u); break;
                        case 3:
                            wp++; watched.Add(u); break;
                    }
                }
                double g = gain(spos, sneg, wp, wn, nwp, nwn);
                if (g > maxGain)
                {
                    maxGain = g;
                    newnode.attr = itemid;
                    newnode.wp = wp;
                    newnode.wn = wn;
                    newnode.nwp = nwp;
                    newnode.nwn = nwn;
                }
            }
            if (maxGain == 0)
            {
                newnode = new Node("", spos > sneg, true, users, target);
                return newnode;
            }
            
            var newattrs = attrs.Where(a => !a.Equals(newnode.attr)).ToList();

            newnode.isLeaf = false;
            newnode.addChild(true, buildTree(watched, newattrs, target, newnode.wp, newnode.wn));
            newnode.addChild(false, buildTree(notwatched, newattrs, target, newnode.nwp, newnode.nwn));
            return newnode;
        }

        public static double entropy(int pos, int neg)
        {
            double tot = pos + neg;
            return -(pos / tot) * Math.Log(pos / tot, 2) - (neg / tot) * Math.Log(neg / tot, 2);
        }

        public static double gain(int spos, int sneg, int wp, int wn, int nwp, int nwn)
        {            
            return entropy(spos, sneg) - ((wp + wn) /(double)(spos + sneg)) * entropy(wp, wn) - ((nwp + nwn) /(double)(spos + sneg)) * entropy(nwp, nwn);
           
        }
    }
    class DT: MemoryBased
    {

        private Dictionary<string, Tree> _trees;
        List<string> itemsIds;
        List<User> _userset;

        public DT(Dictionary<string, User> users, ICollection<IDataRecord> dataset)
            : base(users, dataset)
        {
            this._trees = new Dictionary<string, Tree>();
            this._userset = users.Values.ToList();


            this.itemsIds = dataset.Select(rec => rec.getItemID()).Distinct().ToList();

            //Console.WriteLine("total items: " + itemsIds.Count);
                        
            //foreach (User u in this._userset)
            //{
            //    bool[] bits = new bool[attrs.Count];
            //    for (int i=0; i<attrs.Count;i++)
            //    {
            //        bits[i] = u.getItemRate(attrs[i]) < 0;
            //    }
            //    this._data[u.getUserId()] = new BitArray(bits);
            //}
            //foreach (string itemid in attrs)
            //{
            //    ConstructTree(attrs, itemid);
            //}
        }

        private Tree ConstructDesicionTree(string itemid)
        {
            Console.Out.WriteLine("generating tree for: " + itemid);
            List<string> tmp = new List<string>();
            tmp.Add(itemid);
            Tree resultTree = new Tree(this._userset, itemsIds.Except(tmp).ToList(), itemid);
            //Console.WriteLine(" - depth: " + resultTree.getDepth() + " - size: " + resultTree.getSize());
            return resultTree;
        }

        public override double predictRating(string userID, string itemID)
        {
            if (!this._trees.ContainsKey(itemID))
            {
                this._trees[itemID] = ConstructDesicionTree(itemID);
            }
            Node n = this._trees[itemID].getAnswerNode(this._users[userID]);
            return  n.rate;
        }

        public override double calculateSimilarity(User user, User otherUser)
        {
            return 0d;
        }
        
    }
}
